﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Media;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using LumiSoft.Net;
using LumiSoft.Net.Media;
using LumiSoft.Net.Media.Codec.Audio;
using LumiSoft.Net.RTP;
using LumiSoft.Net.SDP;
using LumiSoft.Net.SIP.Message;
using LumiSoft.Net.SIP.Stack;
using LumiSoft.Net.STUN.Client;
using TEL.ClientClasses.Resources;
using TEL.ClientClasses.SipInterfaces;
using TEL.WinMediaWPF.Audio;

namespace TEL.ClientClasses.Private
{
    public class SipLine
    {
        private int _basePort = 0;
        public SIP_Stack m_pStack;
        private SoundPlayer m_soundPlayer = null;
        private ISipAccount m_property;
        public IOptions Options { get; private set; }
        public SimplePhoneLog Log { get; set; }
        public int AudioDataPort { get; private set; }
        public int AutioControlPort { get; private set; }
        public IPAddress LocalIP { get; private set; }
        public int LineNumber { get; private set; }
        public RTP_AddressSocket RtpAddress { get; private set; }
        public int KillLineNotDialog
        {
            get { return Options.NotActiveLines; }
        }

        public bool Isconference { get; set; }

        public SipLine(int number, IPAddress ipAddress, SIP_Stack stack, ISipAccount property, int basePort)
        {
            LocalIP = ipAddress;
            LineNumber = number;
            _basePort = basePort;
            AudioDataPort = 0;// _basePort + LineNumber * 10;
            AutioControlPort = 0;// AudioDataPort + 1;
            m_pStack = stack;
            m_property = property;


            RtpAddress = new RTP_AddressSocket(LocalIP, AudioDataPort, AutioControlPort);
        }

        public event EventHandler<CallEventArgs> LineStateChanged;
        public event EventHandler<CallEventArgs> CallChanged;
        public event EventHandler<StrErrorEventArgs> Error;
        public event EventHandler<ReferEventArgs> NumEnterTextChanged;
        public event EventHandler<StringEventArgs> AnswerSaveStarting;

        private bool m_isFocus = false;
        public bool IsFocus
        {
            get { return m_isFocus; } 
        
        }

          public void SetFocus(bool value)
           {

               if (!value)
               {
                   // линия не активна
                   switch (State)
                   {
                       case CallState.Hold:
                           break;
                       case CallState.Entity:
                           break;
                       case CallState.Active:
                           if (!Isconference) ToggleCallOnHold();
                           break;
                       case CallState.DialTone:
                           ToggleCallOnHold();
                           break;
                       case CallState.Disposed :
                           CallTerminated();
                           break;
                       default:
                           if (Options.NotActiveLines == 0) CallTerminated();
                           break;
                   }
               }
               else
               {
                   // линия активнва
                   switch (State)
                   {
                       case CallState.Hold:
                          if (!Isconference || m_isFocus)  ToggleCallOnHold(); // active
                           break;
                       case CallState.Entity:
                           ToggleCallOnHold(); // DialTone
                           break;
                       case CallState.Active:
                          if (!Isconference || m_isFocus)  ToggleCallOnHold(); // hold
                           break;
                       case CallState.DialTone: // entity
                           ToggleCallOnHold();
                           break;
                       case CallState.Disposed:
                           CallTerminated();
                           break;
                   }
               }
               m_isFocus = value;
           }

          public void CloseSocket()
        {
            if (RtpAddress.RtpSocket != null)
            {
                if (RtpAddress.RtpSocket.Connected) RtpAddress.RtpSocket.Close();
                RtpAddress.RtpSocket = null;
            }

            if (RtpAddress.RtcpSocket != null)
            {
                if (RtpAddress.RtcpSocket.Connected) RtpAddress.RtcpSocket.Close();
                RtpAddress.RtcpSocket = null;
            }
        }

        public void CreateSocket()
        {
            try
            {
                CloseSocket();

                RtpAddress.RtpSocket = new Socket(RtpAddress.IP.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
                RtpAddress.RtpSocket.Bind(RtpAddress.RtpEP);

                RtpAddress.RtcpSocket = new Socket(RtpAddress.IP.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
                RtpAddress.RtcpSocket.Bind(RtpAddress.RtcpEP);
            }
            catch (Exception error)
            {
                if (Error != null) Error(this, new StrErrorEventArgs("SipLine CreateSocket", error, false));
            }
        }

        public bool IsHasCall
        {
            get { return _call != null; }
        }

        private Call _call = null;
        private Call Call
        {
            get { return _call; }
            set
            {
                _call = value;
                if (_call != null)
                {
                    State = _call.State;
                    _call.StateChanged += new EventHandler(_call_StateChanged);
                    
                }
                else
                {
                    State = CallState.Entity;

                }
                if (CallChanged != null) CallChanged(this, new CallEventArgs(_call));
            }
        }

        public SIP_t_From From
        { get { return IsHasCall ? _call.From : null; } }

        public SIP_t_To To
        { get { return IsHasCall ? _call.To : null;} }

        public string CallID 
        { get { return IsHasCall ? _call.CallID : string.Empty; }}


        public void Transfer(SipLine sipLine)
        {
            try
            {
                if (Call == null) throw new ArgumentNullException("Call");
                if (sipLine == null) throw new ArgumentNullException("SipLine");


                SIP_t_AddressParam contact = new SIP_t_AddressParam();
                contact.Parse(sipLine.GetSipAddress().ToStringValue());

                SIP_Request reinvite = Call.Dialog.CreateRequest(SIP_Methods.REFER);
                reinvite.ContentType = "application/sdp";
                reinvite.ReferTo = contact;
             //   reinvite.ReferredBy = new SIP_t_ReferredBy(SipOperation.MainAccount.AOR);
                SIP_RequestSender requestSender = Call.Dialog.CreateRequestSender(reinvite);
                requestSender.Start();

            }
            catch (Exception error)
            {
                if (Error != null) Error(this, new StrErrorEventArgs("SipLine Refer", error, false));
            }
        }

        public void Xfer(SipLine sipLine)
        {
            try
            {
                if (Call == null) throw new ArgumentNullException("Call");
                if (sipLine == null) throw new ArgumentNullException("SipLine");

                SIP_t_AddressParam contact = new SIP_t_AddressParam();
                string referTo = string.Format("{0}?Replaces={1}%3Bfrom-tag={2}%3Bto-tag={3}",
                                               LineCall, sipLine.CallID, sipLine.From.Tag, sipLine.To.Tag);
              //  referTo = System.Web.HttpUtility.HtmlEncode(referTo);
                SIP_t_NameAddress nameAddress = new SIP_t_NameAddress(referTo);
                contact.Parse(nameAddress.ToStringValue());

                SIP_Request reinvite = Call.Dialog.CreateRequest(SIP_Methods.REFER);
                reinvite.ContentType = "application/sdp";
                reinvite.ReferTo = contact;
                reinvite.ReferredBy = new SIP_t_ReferredBy(SipOperation.MainAccount.AOR);
                SIP_RequestSender requestSender = Call.Dialog.CreateRequestSender(reinvite);
                requestSender.ResponseReceived += new EventHandler<SIP_ResponseReceivedEventArgs>(requestSender_ResponseReceived);
                requestSender.Start();

            }
            catch (Exception error)
            {
                if (Error != null) Error(this, new StrErrorEventArgs("SipLine Refer", error, false));
            }
        }

        void requestSender_ResponseReceived(object sender, SIP_ResponseReceivedEventArgs e)
        {
            if(e.Response.StatusCode == 202)
            {
            }
            KillCall();
        }

        void _call_StateChanged(object sender, EventArgs e)
        {
            if (Call == null) return;

            State = Call.State;

            if (Call.State == CallState.Terminated)
            {
                if (!Call.IsWasActive && Call.IsOutCall && !Call.IsTerminate)
                {
                    Call.RunBusy();
                }
                else
                {
                    KillCall();
                    return;
                }
            }

            if (Call.State == CallState.Disposed)
            {
                if (!(!Call.IsWasActive && Call.IsOutCall))
                    KillCall();
            }
        }

        public void StartWavSaving()
        {
            Call.StartSaving();
        }
        public void StoptWavSaving(bool isCallTerminated)
        {
            try
            {
                if (Call == null)
                    throw new ArgumentNullException(string.Format("калер в линии с номером не найдeна"));

                Call.StopSaving();
                Task task = Task.Factory.StartNew(() => Call.MixInOut());
                if (isCallTerminated)
                {
                    task.Wait();
                    Call = null;
                }

            }
            catch (Exception error)
            {
                if (Error != null) Error(this, new StrErrorEventArgs("SipLine StoptWavSaving", error, false));
            }
        }

        public bool CallTerminated()
        {
            try
            {
                if (Call == null)
                    throw new ArgumentNullException(string.Format("калер в линии с номером не найдeна"));

                if (Call.State != CallState.Disposed)
                {
                    Call.Terminate();
                    return false;
                }
                Call.StopPlayer();
                KillCall();
                return true;

            }
            catch (Exception error)
            {
               if (Error != null) Error(this, new StrErrorEventArgs("SipLine CallTerminated", error,false));
            } return true;
        }

        private void KillCall()
        {
            StopPlayer();
            if (Call.Tags.ContainsKey(Call.MediaSession))
            {
                // CloseSocket();
                MediaSession mediaSession = (MediaSession)Call.Tags[Private.Call.MediaSession];
                mediaSession.MediaStreams.ForEach(x => x.RtpSession.Dispose());
                mediaSession.RTP.Dispose();
                Call.Tags.Remove(Call.MediaSession);
            }

            if (Call.IsSaving)
            {
                StoptWavSaving(true);
            }
            else
            {
                Call = null;
            }
        }

        public string LineCall
        {
            get { return _call == null ? string.Empty : string.Format("{0}", _call.RemoteUri); }
        }

        public string DisplayName
        {
            get { return _call == null ? string.Empty : string.Format("{0}", _call.DisplayName); }
        }

        public void ToggleCallOnHold()
        {

            if (Call != null)
            {
              if(Call.State == CallState.Active || Call.State ==CallState.Hold)  Call.ToggleCallOnHold();
            }
            else
            {
                if (State == CallState.Entity)
                {
                    State = CallState.DialTone;
                    RunSound("dial_tone.wav");
                    return;
                }

                if (State == CallState.DialTone)
                {
                    State = CallState.Entity;
                    StopPlayer();
                    return;
                }
            }
        }

        private CallState _state = CallState.Entity;
        public CallState State
        {
            get
            {
                return _state;
            }
            set
            {
                _state = value;
                if (_state == CallState.Entity)
                {
                    CloseSocket();
                    ClearNumberCall();
                }
                //if (_state == CallState.Active && !IsFocus && m_property.NotActiveLines == 1)
                //    ToggleCallOnHold();
                
                if (LineStateChanged != null)
                    LineStateChanged(this, new CallEventArgs(Call));
            }
        }

       public void StopPlayer()
        {
            try
            {
                if (m_soundPlayer != null)
                {
                    m_soundPlayer.Stop();
                    m_soundPlayer.Dispose();
                }
            }
            catch (Exception error)
            {
                //     if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation StopPlayer", error));
            }

        }

        public void RunSound(string soundName)
        {
            try
            {
                StopPlayer();
                m_soundPlayer = new SoundPlayer(ResManager.GetStream(soundName));
                m_soundPlayer.PlayLooping();

            }
            catch (Exception error)
            {
                //     if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation RunSound", error));
            }
        }

        private List<string> m_RegexNumber = new List<string>();
        public string RegexNumber
        {
            get
            {
                if (m_RegexNumber.Count == 0) return string.Empty;
                string res = string.Empty;
                foreach (string item in m_RegexNumber)
                {
                    res += item;
                }
                return res;
            }
        }

        private void OnEnterChanged()
        {
            if (NumEnterTextChanged != null) NumEnterTextChanged(this, new ReferEventArgs(m_numEnterText, RegexNumber));
        }
        
        private string m_numEnterText = string.Empty;
        public string NumEnterText
        {
            get { return m_numEnterText; }
            set
            {
                m_numEnterText = value;

            }
        }

        public void AddNumber(string simvol, string regex)
        {
            NumEnterText += simvol;
            m_RegexNumber.Add(string.Format("({0})", regex));
            OnEnterChanged();
        }

        public void RemoveLastNum()
        {
            if (!string.IsNullOrEmpty(m_numEnterText))
                NumEnterText = Regex.Replace(m_numEnterText, "\\w$", "");

            if (m_RegexNumber.Count > 0)
                m_RegexNumber.RemoveAt(m_RegexNumber.Count - 1);
            OnEnterChanged();
        }

        public void ClearNumberCall()
        {
            NumEnterText = string.Empty;
            m_RegexNumber.Clear();
            OnEnterChanged();
        }

        public void TonePlay(ClickNumberEventArgs button, Stream stream)
        {
            if (IsHasCall) _call.TonePlay(button, stream);
        }

        public void AcceptCall()
        {
            if (Call == null)
            {
                throw new ArgumentNullException("call");
            }
            if (m_pStack.State != SIP_StackState.Started)
            {
                throw new Exception(" Нет конекта к SIP");
            }

            Account account = (Account)Call.Tags["Account"];
            // isAccept = true;

            SDP_Message sdpOffer = null;
            if (Call.Tags.ContainsKey("SDP_Offer"))
            {
                sdpOffer = (SDP_Message)Call.Tags["SDP_Offer"];
                Call.Tags.Remove("SDP_Offer");
            }

            #region Remote party sent offerless INVITE, we need to send offer to it.
            if (sdpOffer == null)
            {
                //  if (SimpleLog != null) SimpleLog.Write(LogType.Message, "AcceptCall", "SDP_Message is null");
                MediaSession mediaSession = new MediaSession();
                mediaSession.LocalSdpVersion = 1;
                mediaSession.LocalHost = SipOperation.GetLocalIP().ToString();
                mediaSession.RTP = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());

                if (Call.Tags.ContainsKey("MediaSession"))
                    Call.Tags["MediaSession"] = mediaSession;
                else
                    Call.Tags.Add(Private.Call.MediaSession, mediaSession);

                // Add 1 audio stream.
                //   RTP_Session rtpSession = mediaSession.RTP.CreateSession(GetLocalRtpEP("audio", call.LineNumber), new RTP_Clock(new Random().Next(), 8000));
                CreateSocket();
                RTP_Session rtpSession = mediaSession.RTP.CreateSession(RtpAddress, new RTP_Clock(int.MaxValue, 8000));
                rtpSession.Payloads.Add(0, new PCMU());
                rtpSession.Payloads.Add(8, new PCMA());

                //Create audio receiver.
                RTP_AudioPlayer audioPlayer = new RTP_AudioPlayer(SipOperation.AudioOutStram, rtpSession) { Volume = SipOperation.OutVolume };
                //audioPlayer.AudioReceiverEvent += new EventHandler<AudioReceiverEventArgs>(audioPlayer_AudioReceiverEvent);
                //audioPlayer.ReceiverEvent += new EventHandler<MediaLogEventArgs>(audioPlayer_ReceiverEvent);
                //audioPlayer.AudioReceiverErrorEvent += new EventHandler<WinMediaWPF.Audio.ErrorEventArgs>(audioPlayer_AudioReceiverErrorEvent);
                Call.Tags.Add(Private.Call.AudioReceiver, audioPlayer);

                // Create mic sender.
                RTP_AudioSender audioSender = new RTP_AudioSender(rtpSession.CreateSendStream());
                //audioSender.AudioSenderEvent += new EventHandler<AudioSenderEventArgs>(audioSender_AudioSenderEvent);
                audioSender.Source = new AudioIn(SipOperation.AudioInStream, 8000, 16, 1);
                Call.Tags.Add(Private.Call.AudioSender, audioSender);
                Call.SetMicrophone(SipOperation.MicroPhobeActive);
                MediaStream audioStream = new MediaStream(true, SDP_MediaTypes.audio, "RTP/AVP");
                audioStream.MediaFormats.AddRange(new string[] { "0", "8" });
                audioStream.RtpSession = rtpSession;
                audioStream.PublicRtpEP = GetRtpPublicEP(account, audioStream.RtpSession.LocalEP);
                mediaSession.MediaStreams.Add(audioStream);

                // rtpSession.Error += new EventHandler<ExceptionEventArgs>(RtpSession_Error);
                //  rtpSession.ReceiveBufferEvent += new EventHandler<RTP_Session.RTP_ReceiveBufferEventArgs>(RtpSession_ReceiveBufferEvent);
                rtpSession.Start();

                sdpOffer = Private.Call.CreateSDP(mediaSession, RtpAddress, false);
                Call.RtpAddressSocket = RtpAddress;
                Call.Accept(sdpOffer);
            }
            #endregion
            #region Accept call and start media session.
            else
            {
                MediaSession mediaSession = new MediaSession();
                mediaSession.LocalSdpVersion = 1;
                mediaSession.LocalHost = SipOperation.GetLocalIP().ToString();
                mediaSession.RTP = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());
                if (Call.Tags.ContainsKey(Private.Call.MediaSession))
                    Call.Tags[Private.Call.MediaSession] = mediaSession;
                else
                    Call.Tags.Add(Private.Call.MediaSession, mediaSession);

                bool acceptedAudio = false;

                // Process offer media descriptions.
                foreach (SDP_MediaDescription media in sdpOffer.MediaDescriptions)
                {
                    if (media.MediaType == SDP_MediaTypes.audio && string.Equals(media.Protocol, "RTP/AVP", StringComparison.InvariantCultureIgnoreCase))
                    {
                        // See what payloads we can support.
                        List<string> payloads = new List<string>();
                        foreach (string payload in media.MediaFormats)
                        {
                            if (payload == "0")
                            {
                                payloads.Add("0");
                            }
                            else if (payload == "8")
                            {
                                payloads.Add("8");
                            }
                        }

                        // None of the payloads supported, reject this media.
                        if (payloads.Count == 0)
                        {
                            MediaStream mediaStream = new MediaStream(false, media.MediaType, media.Protocol);
                            mediaStream.MediaFormats.AddRange(media.MediaFormats.ToArray());
                            mediaSession.MediaStreams.Add(mediaStream);
                        }
                        // Accept this stream.
                        else
                        {
                            CreateSocket();
                            RTP_Session rtpSession = mediaSession.RTP.CreateSession(RtpAddress, new RTP_Clock(int.MaxValue, 8000));

                            rtpSession.Payload = Convert.ToInt32(payloads[0]);
                            if (payloads.Contains("0"))
                            {
                                rtpSession.Payloads.Add(0, new PCMU());
                            }
                            if (payloads.Contains("8"))
                            {
                                rtpSession.Payloads.Add(8, new PCMA());
                            }
                            //Create audio receiver.
                            RTP_AudioPlayer audioPlayer = new RTP_AudioPlayer(SipOperation.AudioOutStram, rtpSession) { Volume = SipOperation.OutVolume }; ;
                            Call.Tags.Add(Private.Call.AudioReceiver, audioPlayer);
                            Call.SetMicrophone(SipOperation.MicroPhobeActive);
                            // Create mic sender.
                            {
                                RTP_AudioSender audioSender = new RTP_AudioSender(rtpSession.CreateSendStream());
                                // audioSender.AudioSenderEvent += new EventHandler<AudioSenderEventArgs>(audioSender_AudioSenderEvent);
                                audioSender.Source = new AudioIn(SipOperation.AudioInStream, 8000, 16, 1);
                                Call.Tags.Add(Private.Call.AudioSender, audioSender);
                                Call.SetMicrophone(SipOperation.MicroPhobeActive);
                            }
                            // If media has "address" specified, use it, otherwise use global "address".
                            string host = media.Connection != null ? media.Connection.Address : sdpOffer.Connection.Address;
                            if (host != null)
                            {
                                int remoteRtcpPort = media.Port + 1;
                                // Use specified RTCP port, if specified.
                                foreach (SDP_Attribute attribute in media.Attributes)
                                {
                                    if (string.Equals(attribute.Name, "rtcp", StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        remoteRtcpPort = Convert.ToInt32(attribute.Value);
                                        break;
                                    }
                                }

                                // Remove existing target, if it exists.
                                if (rtpSession.Targets.Length > 0)
                                {
                                    rtpSession.RemoveTarget(rtpSession.Targets[0]);
                                }
                                // Add new target.
                                rtpSession.AddTarget(new RTP_Address(System.Net.Dns.GetHostAddresses(host)[0], media.Port, remoteRtcpPort));
                            }

                            // Set stream mode.
                            rtpSession.StreamMode = GetRtpStreamMode(sdpOffer, media);
                            //    rtpSession.Error += new EventHandler<ExceptionEventArgs>(RtpSession_Error);
                            //    rtpSession.ReceiveBufferEvent += new EventHandler<RTP_Session.RTP_ReceiveBufferEventArgs>(RtpSession_ReceiveBufferEvent);
                            rtpSession.Start();

                            MediaStream mediaStream = new MediaStream(true, media.MediaType, media.Protocol);
                            mediaStream.MediaFormats.AddRange(payloads.ToArray());
                            mediaStream.RtpSession = rtpSession;
                            mediaStream.PublicRtpEP = GetRtpPublicEP(account, rtpSession.LocalEP);
                            mediaSession.MediaStreams.Add(mediaStream);

                            acceptedAudio = true;
                        }
                    }
                    // Media type what we are not interested in, reject it.
                    else
                    {

                    }
                }
                Call.RtpAddressSocket = RtpAddress;
                Call.Accept(Private.Call.CreateSDP(mediaSession, RtpAddress, false));
            }
            #endregion
        }

        public void Calling()
        {
            if (m_pStack.State != SIP_StackState.Started)
            {
                return;
            }


            try
            {
                Calling(SipOperation.MainAccount, GetSipAddress());
            }
            catch (Exception error)
            {
                //if (TelSipError != null)
                //    TelSipError(this, new StrErrorEventArgs("SipOperation Call number", error));
            }
        }

        /// <summary>
        /// Calls to the specified person.
        /// </summary>
        /// <param name="account">Local account to use.</param>
        /// <param name="to">Call target address.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>account</b> or <b>to</b> is null reference.</exception>
        private void Calling(Account account, SIP_t_NameAddress to)
        {
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }
            if (to == null)
            {
                throw new ArgumentNullException("to");
            }
            if (m_pStack.State != SIP_StackState.Started)
            {
                return;
            }
            bool sips = false;
            if (string.Equals(to.Uri.Scheme, "sips", StringComparison.InvariantCultureIgnoreCase))
            {
                sips = true;
            }

            RTP_AudioSender audioSender = null;
            RTP_AudioPlayer audioPlayer = null;

            MediaSession mediaSession = new MediaSession();
            mediaSession.LocalSdpVersion = 1;
            mediaSession.LocalHost = SipOperation.GetLocalIP().ToString();
            mediaSession.RTP = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());
            CreateSocket();
            RTP_Session rtpSession = mediaSession.RTP.CreateSession(RtpAddress, new RTP_Clock(int.MaxValue, 8000));
       
            rtpSession.Payloads.Add(0, new PCMU());
            rtpSession.Payloads.Add(8, new PCMA());
            MediaStream audioStream = new MediaStream(true, SDP_MediaTypes.audio, "RTP/AVP");
            audioStream.MediaFormats.AddRange(new string[] { "0", "8", "101" });
            audioStream.RtpSession = rtpSession;
            audioStream.PublicRtpEP = GetRtpPublicEP(account, audioStream.RtpSession.LocalEP);
            mediaSession.MediaStreams.Add(audioStream);

            SDP_Message sdpOffer = Private.Call.CreateSDP(mediaSession, RtpAddress, false);
           
            SIP_Request invite = m_pStack.CreateRequest(SIP_Methods.INVITE, to,
                                                        new SIP_t_NameAddress(account.DisplayName,
                                                                              AbsoluteUri.Parse((sips ? "sips:" : "sip:") +
                                                                                                account.AOR)));
            //invite.Authorization.Add();
            if (account.UseProxy)
            {
                invite.Route.Add("<" + account.ProxyServer + ";lr>");
            }
         
            invite.ContentType = "application/sdp";
            invite.Data = sdpOffer.ToByte();

            // Create and initialize call.
            DateTime d = DateTime.Now;

            audioSender = new RTP_AudioSender(rtpSession.CreateSendStream());
            audioSender.Source = new AudioIn(SipOperation.AudioInStream, 8000, 16, 1);
            audioPlayer = new RTP_AudioPlayer(SipOperation.AudioOutStram, rtpSession) { Volume = SipOperation.OutVolume };
            
            TimeSpan ts = DateTime.Now - d;

            Call = new Call(m_pStack, invite);
            Call.AudioOutStram = SipOperation.AudioOutStram;
            Call.Tags.Add("Account", account);
            Call.Tags.Add(Private.Call.MediaSession, mediaSession);
            Call.Tags.Add(Private.Call.AudioReceiver, audioPlayer);
            Call.Tags.Add(Private.Call.AudioSender, audioSender);
            Call.Tags.Add("AudioIn", audioSender.Source);
            Call.SetMicrophone(SipOperation.MicroPhobeActive);
            Call.RtpAddressSocket = RtpAddress;
            Call.SignalVolume = SignalVolume;
            Call.MediaAnswer += new EventHandler<EventArgs<SDP_Message>>(Call_MediaAnswer);
            Call.LogEvent += new EventHandler<LogEventArgs>(Call_LogEvent);
            Call.IsOutCall = true;
            // Call.Error += call_Error;
            // добавить новый калер в коллекцию

            Call.Start();
        }

        void Call_LogEvent(object sender, LogEventArgs e)
        {
           if(Log!= null) Log.WriteEvent(sender,e);
        }

        private void Call_MediaAnswer(object sender, EventArgs<SDP_Message> e)
        {
            try
            {
                Call call = (Call)sender;
                ProcessSdpAnswer(call, e.Value);
            }
            catch (Exception error)
            {
                //if (TelSipError != null)
                //    TelSipError(this, new StrErrorEventArgs("SipOperation Call_MediaAnswer", error));
            }
        }

        /// <summary>
        /// Processes SDP answer and updates media session based on it.
        /// </summary>
        /// <param name="call">Call.</param>
        /// <param name="sdpAnswer">SDP answer.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>call</b> or <b>sdpAnswer</b> is null reference.</exception>
        private void ProcessSdpAnswer(Call call, SDP_Message sdpAnswer)
        {
            if (call == null)
            {
                throw new ArgumentNullException("call");
            }
            if (sdpAnswer == null)
            {
                throw new ArgumentNullException("sdpAnswer");
            }

            try
            {
                MediaSession mediaSession = (MediaSession)call.Tags[Private.Call.MediaSession];

                // Remote party din't change session, this is answer to our session refresh request.
                if (mediaSession.RemoteSdpVersion == sdpAnswer.Origin.SessionVersion)
                {
                    return;
                }

                // Media descriptions count in answer must be equal to media offer(RFC 3261 6.).
                if (mediaSession.MediaStreams.Count != sdpAnswer.MediaDescriptions.Count)
                {
                    call.Terminate("Invalid SDP answer, media descriptions count in answer must be equal to media offer(RFC 3261 6.).");

                    return;
                }

                // Udpate media session based on SDP answer.
                for (int i = 0; i < mediaSession.MediaStreams.Count; i++)
                {
                    MediaStream offerMedia = mediaSession.MediaStreams[i];
                    SDP_MediaDescription answerMedia = sdpAnswer.MediaDescriptions[i];

                    // This stream rejected by remote party.
                    if (answerMedia.Port == 0)
                    {
                        offerMedia.IsUsed = false;

                        // Release RTP session.
                        if (offerMedia.RtpSession != null)
                        {
                            offerMedia.RtpSession.Dispose();
                            offerMedia.RtpSession = null;
                            offerMedia.PublicRtpEP = null;
                        }
                    }
                    else
                    {
                        offerMedia.IsUsed = true;
                        offerMedia.MediaFormats.Clear();
                        offerMedia.RtpSession.Payloads.Clear();

                        // Add only payloads what we can support from answer.
                        foreach (string payload in answerMedia.MediaFormats)
                        {
                            if (payload == "0")
                            {
                                offerMedia.MediaFormats.Add("0");
                                offerMedia.RtpSession.Payloads.Add(0, new PCMU());
                            }
                            else if (payload == "8")
                            {
                                offerMedia.MediaFormats.Add("8");
                                offerMedia.RtpSession.Payloads.Add(8, new PCMA());
                            }
                            else if (payload == "101")
                            {
                                offerMedia.MediaFormats.Add("101");
                                offerMedia.RtpSession.Payloads.Add(101, new TE101Codec());
                            }
                        }

                        // If media has "address" specified, use it, otherwise use global "address".
                        string host = answerMedia.Connection != null ? answerMedia.Connection.Address : sdpAnswer.Connection.Address;
                        if (host != null)
                        {
                            int remoteRtcpPort = answerMedia.Port + 1;
                            // Use specified RTCP port, if specified.
                            foreach (SDP_Attribute attribute in answerMedia.Attributes)
                            {
                                if (string.Equals(attribute.Name, "rtcp", StringComparison.InvariantCultureIgnoreCase))
                                {
                                    remoteRtcpPort = Convert.ToInt32(attribute.Value);
                                    break;
                                }
                            }

                            // Remove existing target, if it exists.
                            if (offerMedia.RtpSession.Targets.Length > 0)
                            {
                                offerMedia.RtpSession.RemoveTarget(offerMedia.RtpSession.Targets[0]);
                            }

                            // Add new target.
                            offerMedia.RtpSession.AddTarget(new RTP_Address(System.Net.Dns.GetHostAddresses(host)[0], answerMedia.Port, remoteRtcpPort));
                        }

                        // TODO: Handle sendonly,receiveonly,sendreceive.
                        RTP_StreamMode remotePartyStreamMode = GetRtpStreamMode(sdpAnswer, answerMedia);
                        //if(offerMedia.RtpSession.
                        //if(remotePartyStreamMode == RTP_StreamMode.){
                        //}
                        offerMedia.RtpSession.StreamMode = remotePartyStreamMode;

                        offerMedia.RtpSession.Payload = Convert.ToInt32(answerMedia.MediaFormats[0]);

                        offerMedia.RtpSession.Start();
                    }
                }

                mediaSession.RemoteSdpVersion = sdpAnswer.Origin.SessionVersion;
            }
            catch (Exception exception)
            {
                call.Terminate("Internal error: " + exception.Message);
                // if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation ProcessSdpAnswer", exception));
            }
        }

        /// <summary>
        /// Resolves local RTP end point to publicend point.
        /// </summary>
        /// <param name="account">Local account.</param>
        /// <param name="localEP">Local RTP end point.</param>
        /// <returns>Returns public RTP end point.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>account</b> or <b>localEP</b> is null reference.</exception>
        private RTP_Address GetRtpPublicEP(Account account, RTP_Address localEP)
        {
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }
            if (localEP == null)
            {
                throw new ArgumentNullException("localEP");
            }

            /* NAT resolving.
                1) UPnP open NAT.
                2) STUN.
            */

            // Get RTP and RTCP public IP:port.
            if (!string.IsNullOrEmpty(account.StunServer))
            {
                IPEndPoint publicRtpEP = null;
                IPEndPoint publicRtcpEP = null;

                STUN_Result result = STUN_Client.Query(account.StunServer, 3478, new IPEndPoint(localEP.IP, localEP.DataPort));
                if (result.PublicEndPoint != null)
                {
                    publicRtpEP = result.PublicEndPoint;
                }
                result = STUN_Client.Query(account.StunServer, 3478, new IPEndPoint(localEP.IP, localEP.ControlPort));
                if (result.PublicEndPoint != null)
                {
                    publicRtcpEP = result.PublicEndPoint;
                }

                if (publicRtpEP != null && publicRtcpEP != null)
                {
                    return new RTP_Address(publicRtpEP.Address, publicRtpEP.Port, publicRtcpEP.Port);
                }
            }
            return localEP;
        }

        /// <summary>
        /// Gets RTP stream mode for the specified media.
        /// </summary>
        /// <param name="sdp">SDP message.</param>
        /// <param name="media">Media stream.</param>
        /// <returns>Returns RTP stream mode.</returns>
        private RTP_StreamMode GetRtpStreamMode(SDP_Message sdp, SDP_MediaDescription media)
        {
            if (sdp == null)
            {
                throw new ArgumentNullException("sdp");
            }
            if (media == null)
            {
                throw new ArgumentNullException("media");
            }

            // Search sendrecv,sendonly,recvonly,inactive media level attribute.
            foreach (SDP_Attribute a in media.Attributes)
            {
                if (string.Equals(a.Name, "sendrecv", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.SendReceive;
                }
                else if (string.Equals(a.Name, "sendonly", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.Receive;
                }
                else if (string.Equals(a.Name, "recvonly", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.Send;
                }
                else if (string.Equals(a.Name, "inactive", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.Inactive;
                }
            }

            // If we reach so far then default(sendrecv) or this attribute is at session level.
            foreach (SDP_Attribute a in sdp.Attributes)
            {
                if (string.Equals(a.Name, "sendrecv", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.SendReceive;
                }
                else if (string.Equals(a.Name, "sendonly", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.Receive;
                }
                else if (string.Equals(a.Name, "recvonly", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.Send;
                }
                else if (string.Equals(a.Name, "inactive", StringComparison.InvariantCultureIgnoreCase))
                {
                    return RTP_StreamMode.Inactive;
                }
            }

            // We have default(sendrecv).

            return RTP_StreamMode.SendReceive;
        }

        public SIP_t_NameAddress GetSipAddress()
        {

            if (m_property == null)
            {
                throw new ArgumentNullException("Property");
            }
            if (IsHasCall)
                return new SIP_t_NameAddress(LineCall);
            if (string.IsNullOrEmpty(NumEnterText))
            {
                throw new ArgumentNullException("NumEnterText");
            }
            return new SIP_t_NameAddress(string.Format("sip:{0}@{1}", NumEnterText, this.m_property.Host));
        }

        private void AnswerMashine()
        {

            if (Call == null)
            {
                throw new ArgumentNullException("call");
            }

            Account account = (Account)Call.Tags["Account"];

            SDP_Message sdpOffer = null;
            if (Call.Tags.ContainsKey("SDP_Offer"))
            {
                sdpOffer = (SDP_Message)Call.Tags["SDP_Offer"];
                Call.Tags.Remove("SDP_Offer");
            }

            // Remote party sent offerless INVITE, we need to send offer to it.
            if (sdpOffer == null)
            {
                MediaSession mediaSession = new MediaSession();
                mediaSession.LocalSdpVersion = 1;
                mediaSession.LocalHost = SipOperation.GetLocalIP().ToString();
                mediaSession.RTP = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());
                Call.Tags.Add(Private.Call.MediaSession, mediaSession);

                // Add 1 audio stream.
                //RTP_Session rtpSession = mediaSession.RTP.CreateSession(GetLocalRtpEP("audio", call.LineNumber),
                //                                                        new RTP_Clock(new Random().Next(), 8000));

                RTP_Session rtpSession = mediaSession.RTP.CreateSession(RtpAddress, new RTP_Clock(int.MaxValue, 8000));
                rtpSession.Payloads.Add(0, new PCMU());
                rtpSession.Payloads.Add(8, new PCMA());
                //Create audio receiver.
                // call.Tags.Add(Private.Call.AudioReceiver, new RTP_AudioPlayer(AudioOutStram, rtpSession));
                // Create mic sender.
                RTP_AudioSender audioSender = new RTP_AudioSender(rtpSession.CreateSendStream());
                audioSender.IsAnswer = true;
                audioSender.LineCall = Call.RemoteUri.ToString();
                audioSender.AnswerEnd += new EventHandler<RTP_AudioSender.StringEventArgs>(audioSender_AnswerEnd);
                audioSender.Source = audioSender.LoadAnswer("AnswerMashiceContent.wav");
                Call.Tags.Add(Private.Call.AudioSender, audioSender);
                Call.SetMicrophone(SipOperation.MicroPhobeActive);

                MediaStream audioStream = new MediaStream(true, SDP_MediaTypes.audio, "RTP/AVP");
                audioStream.MediaFormats.AddRange(new string[] { "0", "8" });
                audioStream.RtpSession = rtpSession;
                audioStream.PublicRtpEP = GetRtpPublicEP(account, audioStream.RtpSession.LocalEP);
                mediaSession.MediaStreams.Add(audioStream);

                sdpOffer = Private.Call.CreateSDP(mediaSession, RtpAddress, false);
                Call.RtpAddressSocket = RtpAddress;
                Call.Accept(sdpOffer);
            }
            // Accept call and start media session.
            else
            {
                MediaSession mediaSession = new MediaSession();
                mediaSession.LocalSdpVersion = 1;
                mediaSession.LocalHost = SipOperation.GetLocalIP().ToString();
                mediaSession.RTP = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());
                Call.Tags.Add(Private.Call.MediaSession, mediaSession);

                bool acceptedAudio = false;
                
                // Process offer media descriptions.
                foreach (SDP_MediaDescription media in sdpOffer.MediaDescriptions)
                {
                    if (media.MediaType == SDP_MediaTypes.audio &&
                        string.Equals(media.Protocol, "RTP/AVP", StringComparison.InvariantCultureIgnoreCase))
                    {
                        // See what payloads we can support.
                        List<string> payloads = new List<string>();
                        foreach (string payload in media.MediaFormats)
                        {
                            if (payload == "0")
                            {
                                payloads.Add("0");
                            }
                            else if (payload == "8")
                            {
                                payloads.Add("8");
                            }
                        }

                        // None of the payloads supported, reject this media.
                        if (payloads.Count == 0)
                        {
                            MediaStream mediaStream = new MediaStream(false, media.MediaType, media.Protocol);
                            mediaStream.MediaFormats.AddRange(media.MediaFormats.ToArray());
                            mediaSession.MediaStreams.Add(mediaStream);
                        }
                        // Accept this stream.
                        else
                        {
                            // Create RTP session.
                            //RTP_Session rtpSession = mediaSession.RTP.CreateSession(GetLocalRtpEP(media.MediaType, call.LineNumber),
                            //                                   new RTP_Clock(new Random().Next(), 8000));
                            RTP_Session rtpSession = mediaSession.RTP.CreateSession(RtpAddress, new RTP_Clock(int.MaxValue, 8000));
                            rtpSession.Payload = Convert.ToInt32(payloads[0]);
                            if (payloads.Contains("0"))
                            {
                                rtpSession.Payloads.Add(0, new PCMU());
                            }
                            if (payloads.Contains("8"))
                            {
                                rtpSession.Payloads.Add(8, new PCMA());
                            }
                            //Create audio receiver.
                            Call.Tags.Add(Private.Call.AudioReceiver, new RTP_AudioPlayer(SipOperation.AudioOutStram, rtpSession) { Volume = SipOperation.OutVolume });
                            // Create mic sender.
                            {
                                RTP_AudioSender audioSender = new RTP_AudioSender(rtpSession.CreateSendStream());
                                audioSender.IsAnswer = true;
                                audioSender.LineCall = Call.RemoteUri.ToString();
                                audioSender.AnswerEnd +=
                                    new EventHandler<RTP_AudioSender.StringEventArgs>(audioSender_AnswerEnd);
                                audioSender.Source = audioSender.LoadAnswer("AnswerMashiceContent.wav");
                                // ResManager.GetStream("AnswerMashiceContent.wav");
                                Call.Tags.Add(Private.Call.AudioSender, audioSender);
                                Call.SetMicrophone(SipOperation.MicroPhobeActive);
                            }
                            // If media has "address" specified, use it, otherwise use global "address".
                            string host = media.Connection != null
                                              ? media.Connection.Address
                                              : sdpOffer.Connection.Address;
                            if (host != null)
                            {
                                int remoteRtcpPort = media.Port + 1;
                                // Use specified RTCP port, if specified.
                                foreach (SDP_Attribute attribute in media.Attributes)
                                {
                                    if (string.Equals(attribute.Name, "rtcp",
                                                      StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        remoteRtcpPort = Convert.ToInt32(attribute.Value);
                                        break;
                                    }
                                }

                                // Remove existing target, if it exists.
                                if (rtpSession.Targets.Length > 0)
                                {
                                    rtpSession.RemoveTarget(rtpSession.Targets[0]);
                                }
                                // Add new target.
                                rtpSession.AddTarget(new RTP_Address(System.Net.Dns.GetHostAddresses(host)[0],
                                                                     media.Port, remoteRtcpPort));
                            }

                            // Set stream mode.
                            rtpSession.StreamMode = GetRtpStreamMode(sdpOffer, media);

                            rtpSession.Start();

                            MediaStream mediaStream = new MediaStream(true, media.MediaType, media.Protocol);
                            mediaStream.MediaFormats.AddRange(payloads.ToArray());
                            mediaStream.RtpSession = rtpSession;
                            mediaStream.PublicRtpEP = GetRtpPublicEP(account, rtpSession.LocalEP);
                            mediaSession.MediaStreams.Add(mediaStream);

                            acceptedAudio = true;
                        }
                    }
                    // Media type what we are not interested in, reject it.
                    else
                    {

                    }
                }
                Call.RtpAddressSocket = RtpAddress;
                Call.Accept(Private.Call.CreateSDP(mediaSession, RtpAddress, false));
            }

        }

        void audioSender_AnswerEnd(object sender, RTP_AudioSender.StringEventArgs e)
        {
            try
            {
                RTP_AudioSender audioSender = sender as RTP_AudioSender;
                if (audioSender == null)
                {
                    return;
                }
                audioSender.AnswerFileName = string.Empty;
                if (e.Message == "AnswerMashiceContent.wav")
                {
                    audioSender.AnswerFileName = "tone";
                    Stream stream = SipOperation.generalTone('*');
                    stream.Position = 0;
                    audioSender.Source = stream;
                }
                if (e.Message == "tone")
                {
                    if (AnswerSaveStarting != null) AnswerSaveStarting(this, new StringEventArgs(audioSender.LineCall));
                }
            }
            catch (Exception error)
            {
            }
        }

        private int mSignalVolume = 100;
        public int SignalVolume
        {
            get { return mSignalVolume; }
            set
            {
                mSignalVolume = value;
                if (Call != null) Call.SignalVolume = value;
            }
        }

        public void Invite(SIP_RequestReceivedEventArgs e)
        {
            Call = new Call(m_pStack, e.ServerTransaction);
            Call.AudioOutStram = SipOperation.AudioOutStram;
            Call.Tags.Add("Account", SipOperation.MainAccount);
            Call.LogEvent += new EventHandler<LogEventArgs>(Call_LogEvent);
            Call.SignalVolume = SignalVolume;
            // INVITE has media offer.
            if (e.Request.ContentType != null &&
                e.Request.ContentType.ToLower().IndexOf("application/sdp") > -1)
            {
                SDP_Message sdpOffer = SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data));

                bool canSupportOffer = false;
                // Check if i can accept at least 1 media stream from offer.
                foreach (SDP_MediaDescription media in sdpOffer.MediaDescriptions)
                {
                    // RTP audio stream.
                    if (
                        string.Equals(media.MediaType, SDP_MediaTypes.audio,
                                      StringComparison.InvariantCultureIgnoreCase) &&
                        string.Equals(media.Protocol, "RTP/AVP",
                                      StringComparison.InvariantCultureIgnoreCase))
                    {
                        // See if we can support any payload.
                        foreach (string payload in media.MediaFormats)
                        {
                            if (payload == "0" || payload == "8")
                            {
                                canSupportOffer = true;
                                break;
                            }
                        }
                    }
                }

                // We can support offer, reject call.
                if (!canSupportOffer)
                {
                    Call.Reject(SIP_ResponseCodes.x488_Not_Acceptable_Here);

                    return;
                }

                // Store offer and answer to call.
                Call.Tags.Add("SDP_Offer", sdpOffer);
            }
            Call.SendRinging(null);
        }

        public void MicrophoneActiveToCall(bool value)
        {
            _call.SetMicrophone(value);
        }

        public void SetVolume(int volume)
        {
            try
            {
                if (Call != null)
                {
                    Call.SetVolume( volume);
                }
            }
            catch (Exception) { }
        }
    }
}
